package com.wh1200.mybatis.xmlless.resolver

import com.wh1200.kotlin.toCamelCase
import com.wh1200.mybatis.xmlless.constant.ClassNames
import com.wh1200.mybatis.xmlless.constant.PAGEABLE_SORT
import com.wh1200.mybatis.xmlless.enums.MethodType
import com.wh1200.mybatis.xmlless.enums.XmllessDbType
import com.wh1200.mybatis.xmlless.kotlin.split
import com.wh1200.mybatis.xmlless.model.*
import com.wh1200.mybatis.xmlless.model.base.BaseCriteria
import com.wh1200.mybatis.xmlless.model.base.BaseMethodWrapper
import com.wh1200.mybatis.xmlless.model.component.ReplacePropertyToColumnVisitor
import net.sf.jsqlparser.expression.CastExpression
import net.sf.jsqlparser.parser.CCJSqlParserUtil
import net.sf.jsqlparser.schema.Column
import net.sf.jsqlparser.statement.select.PlainSelect
import net.sf.jsqlparser.statement.select.Select

/**
 *
 * @author 吴昊
 * @since 0.0.1
 */
object QueryResolver {

  private val QUERY_CACHE = hashMapOf<String, ResolvedQuery>()
  private val MULTIPLE_SPACE_REG = "(?U)\\s+".toRegex()

  fun resolveMethodInfo(
      method: BaseMethodWrapper,
      tableInfo: XmllessTableInfo,
      dbType: XmllessDbType,
      useCache: Boolean = true
  ): MethodInfo {
    val returnMappings = method.resolveReturnMappingList()
    val modelMappings = MappingResolver.resolve(tableInfo, useCache)
    val finalMappings = if (returnMappings != null) {
      if (returnMappings.modelClassName in modelMappings.mappings.mapNotNull { it.joinInfo }
              .filterIsInstance<ObjectJoinInfo>().map { it.joinEntityName }) {
        modelMappings
      } else {
        FieldMappingList(
            returnMappings.mappings, modelMappings.tableInfo,
            modelMappings.tableInfo.entityTypeName
        )
      }
    } else {
      modelMappings
    }
    val returnType = method.getReturnType()
    val parameters = method.getParameters()
    val xmllessMethod = method.getXmllessMethod()
    return MethodInfo(
        method.getModelName(),
        method.getModelFields(),
        returnType,
        finalMappings,
        modelMappings, dbType,
        method.getFullName(),
        method.getPropertiesMapping() + xmllessMethod.getPropertyMapping(),
        method.isJsonResult(),
        method.getResultMap(),
        method.getExcludeProperties(),
        method.getSelectedProperties(),
        method.getName(),
        modelMappings.tableInfo.logicDeleteFieldInfo != null
            && !method.isLogicDisabled(),
        parameters,
        method.getOptionalParameterNames(),
        xmllessMethod,
        ParameterResolver.createOptionalParameters(parameters)
    )
  }

  fun resolveQuery(
      methodInfo: MethodInfo,
      useCache: Boolean = true
  ): ResolvedQuery {
    if (getQueryCache(methodInfo.fullMethodName) != null && useCache) {
      return getQueryCache(methodInfo.fullMethodName)!!
    }
    val result = NameResolver(methodInfo).resolve()
    val query = Query(
        methodInfo,
        result.queryType,
        Properties(
            result.properties,
            result.excludeProperties
        ),
        result.conditions,
        result.sorts,
        methodInfo.groupBy + result.groupBy,
        result.havingConditions
    )
    methodInfo.parameters.forEachIndexed { index, param ->
      val isPageable = param.allSuperClassOrInterfaces.contains(ClassNames.SPRING_PAGEABLE)
      if (isPageable) {
        val paramName = methodInfo.paramNames[index]
        query.limitation = Limitation("$paramName.offset", "$paramName.pageSize", true, paramName)
        query.extraSortScript = PAGEABLE_SORT.format(paramName, paramName)
      }
    }
    val resolvedQuery = ResolvedQuery(
        query,
        methodInfo
    )
    putQueryCache(methodInfo.fullMethodName, resolvedQuery)
    return resolvedQuery
  }

  fun resolveSortFromExpression(
      str: String, methodInfo: MethodInfo
  ): SortInfo {
    val exp =
        (CCJSqlParserUtil.parseStatements("select * from ${methodInfo.tableInfo.tableName} order by $str").statements.first() as Select).selectBody as PlainSelect
    val orderBy = exp.orderByElements.first()
    val expression = orderBy.expression
    val visitor = ReplacePropertyToColumnVisitor(methodInfo)
    when (expression) {
      is Column         -> visitor.visit(expression)
      is CastExpression -> visitor.visit(expression)
      else              -> throw IllegalArgumentException("不支持的排序表达式：$str")
    }
    return SortInfo(orderBy.toString(), orderBy.isAsc, SortInfo.Mode.Expression)
  }

  fun resolveSortFromExpression(
      str: String
  ): List<SortInfo> {
    val orderByString = if (str.startsWith("OrderBy")) {
      str.drop(7)
    } else if (str.replace(MULTIPLE_SPACE_REG, " ").lowercase().startsWith("order by ")) {
      str.replace(MULTIPLE_SPACE_REG, " ").drop("order by ".length)
    } else {
      str
    }
    if (orderByString.contains(" ")) {
      return resolveFromSpaceSplitExpression(orderByString, str)
    }
    val sortStrings = orderByString.split("And").filter { it.isNotBlank() }
    return sortStrings.map {
      var isAsc = true
      val sortProperty = when {
        it.endsWith("Desc") -> {
          isAsc = false
          it.substring(0, it.length - 4)
        }

        it.endsWith("Asc")  -> it.substring(0, it.length - 3)
        else                -> it
      }
      SortInfo(sortProperty.toCamelCase(), isAsc)
    }
  }

  fun resolveResultMap(
      methodInfo: MethodInfo, query: Query
  ): String? {
    val resultMap = methodInfo.resultMap.firstOrNull()
    if (resultMap == null && query.type == MethodType.Select && methodInfo.returnType != null) {
      // 如果没有指定resultMap，则自动生成resultMap
      return ResultMapResolver.resolveResultMap(
          methodInfo.fullMethodName,
          methodInfo.returnType, query, Properties(), methodInfo.isJsonResult
      )
    }
    return resultMap
  }

  @Suppress("unused")
  private fun getQueryCache(key: String): ResolvedQuery? {
    return QUERY_CACHE[key]
  }

  private fun putQueryCache(key: String, query: ResolvedQuery) {
    QUERY_CACHE[key] = query
  }

  private fun resolveFromSpaceSplitExpression(
      orderByString: String,
      str: String
  ): List<SortInfo> {
    val words = orderByString.replace(" and ", " AND ").split(" ")
    return words.split("AND").map {
      if (it.size == 2) {
        if (it[1] in listOf("asc", "ASC")) {
          SortInfo(it.first(), true)
        } else if (it[1] in listOf("desc", "DESC")) {
          SortInfo(it.first(), false)
        } else {
          error("从表达式【${str}】中解析排序条件失败")
        }
      } else if (it.size == 1) {
        SortInfo(it.first(), true)
      } else {
        error("从表达式【${str}】中解析排序条件失败")
      }
    }
  }

  /**
   * @author 吴昊
   * @date 2020/01/15
   * @version 1.0
   * @since v1.0
   */
  data class NameResolveResult(
      val queryType: MethodType,
      val properties: List<String>,
      val excludeProperties: List<String>,
      val conditions: List<BaseCriteria>,
      val sorts: List<SortInfo>,
      val groupBy: List<String>,
      val havingConditions: List<BaseCriteria>
  )

}
